package com.main;

import java.nio.FloatBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Quaternion;
import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Vector4f;

/**
 * Need to add roll(), lookAt(), and maybe SLERP
 * 
 * @author Craig Henderson
 * 
 */

class Camera {
	private final static float PI_OVER_180 = 0.0174532925f; // ...convert
															// between degrees
															// and radians
	private Vector3f position = new Vector3f();
	private Vector3f direction = new Vector3f();
	private FloatBuffer viewMatrixBuffer = BufferUtils.createFloatBuffer(4 * 4);

	private float pitchAngle;
	private float bearingAngle;
	private Quaternion pitch;
	private Quaternion bearing;
	private Quaternion rotation;

	public Camera() {
		pitch = new Quaternion();
		bearing = new Quaternion();
		rotation = new Quaternion();
		bearingAngle = 0;
		pitchAngle = 0;
	}

	/**
	 * Constructor with initial direction values
	 * 
	 * @param initialBearing
	 * @param initialPitch
	 */
	public Camera(float initialBearing, float initialPitch) {
		pitch = new Quaternion();
		bearing = new Quaternion();
		rotation = new Quaternion();
		bearingAngle = initialBearing;
		pitchAngle = initialPitch;
	}

	/**
	 * This should be called whenever pitch, bearing, or roll is changed to
	 * recalculate the matrix
	 */
	public void reorient() {
		// quaternion multiply is non-commutative (very important)...
		Quaternion.mul(pitch, bearing, rotation);

		// orient the camera...
		Matrix4f matrix = convertQuaternionToMatrix4f(rotation);
		matrix.store(viewMatrixBuffer);
		viewMatrixBuffer.rewind();

		// calculate direction vector...
		Matrix4f pitchMatrix = convertQuaternionToMatrix4f(pitch);
		Quaternion temp = Quaternion.mul(bearing, pitch, null);
		matrix = convertQuaternionToMatrix4f(temp);
		direction.x = matrix.m20;
		direction.y = pitchMatrix.m21;
		direction.z = matrix.m22;
	}

	/**
	 * Change the bearing (yaw)
	 * 
	 * @param bearing
	 *            delta in degrees
	 */
	public void bearing(float bearingDelta) {
		bearingAngle += bearingDelta;
		bearing.setFromAxisAngle(new Vector4f(0f, 1f, 0f, bearingAngle
				* PI_OVER_180));
	}

	/**
	 * Change the pitch
	 * 
	 * @param pitch
	 *            delta in degrees
	 */
	public void pitch(float pitchDelta) {
		pitchAngle += pitchDelta;
		pitch.setFromAxisAngle(new Vector4f(1f, 0f, 0f, pitchAngle * PI_OVER_180));
	}

	// /**
	// * Change direction to focus on a certain point in the world
	// * @param position
	// */
	// public void lookAt(Vector3f position)
	// {
	// GLU.glLookAt
	// }

	/**
	 * Move in the direction of view by a certain amount
	 * 
	 * @param units
	 */
	public void move(float units) {
		position.x += direction.x * units;
		position.y += direction.y * units;
		position.z += direction.z * units;
	}

	/**
	 * Move side to side
	 * 
	 * @param units
	 */
	public void strafe(float units) {
		Vector3f up = new Vector3f(0, 1, 0); // ...I would have made this a
												// constant, except that if ever
												// i do roll(), it won't be
		Vector3f cross = Vector3f.cross(direction, up, null);

		position.x += cross.x * units;
		position.y += cross.y * units;
		position.z += cross.z * units;
	}

	/**
	 * @return the camera's position in the world
	 */
	public Vector3f getPosition() {
		return position;
	}

	/**
	 * Place camera at a certain position
	 * 
	 * @param position
	 */
	public void setPosition(Vector3f position) {
		this.position = position;
	}

	public Vector3f getDirection() {
		return direction;
	}

	/**
	 * Call GL11.glMultMatrix() on this matrix in your render loop to set the
	 * camera's view.
	 * 
	 * @return buffer of a 4x4 matrix for the view transformation
	 */
	public FloatBuffer getViewMatrixBuffer() {
		return viewMatrixBuffer;
	}

	private static Matrix4f convertQuaternionToMatrix4f(Quaternion q) {
		Matrix4f matrix = new Matrix4f();
		matrix.m00 = 1.0f - 2.0f * (q.getY() * q.getY() + q.getZ() * q.getZ());
		matrix.m01 = 2.0f * (q.getX() * q.getY() + q.getZ() * q.getW());
		matrix.m02 = 2.0f * (q.getX() * q.getZ() - q.getY() * q.getW());
		matrix.m03 = 0.0f;

		// Second row
		matrix.m10 = 2.0f * (q.getX() * q.getY() - q.getZ() * q.getW());
		matrix.m11 = 1.0f - 2.0f * (q.getX() * q.getX() + q.getZ() * q.getZ());
		matrix.m12 = 2.0f * (q.getZ() * q.getY() + q.getX() * q.getW());
		matrix.m13 = 0.0f;

		// Third row
		matrix.m20 = 2.0f * (q.getX() * q.getZ() + q.getY() * q.getW());
		matrix.m21 = 2.0f * (q.getY() * q.getZ() - q.getX() * q.getW());
		matrix.m22 = 1.0f - 2.0f * (q.getX() * q.getX() + q.getY() * q.getY());
		matrix.m23 = 0.0f;

		// Fourth row
		matrix.m30 = 0;
		matrix.m31 = 0;
		matrix.m32 = 0;
		matrix.m33 = 1.0f;

		return matrix;
	}

}
